package com.eic.framework.core.security;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.eic.framework.security.LoginJwtHandler;
import com.eic.framework.thread.ThreadPools;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 用户在线缓存
 */
public class SecuritySession {
    private static Logger logger= LoggerFactory.getLogger(SecuritySession.class);
    private static Map<String,Long> cacheMap=new HashMap<>();
    private static Set<String> cacheNameSet=new HashSet<>();
    private static long expireMillseconds=30*60*1000;
    private static RedisCache cache;
    private static boolean online=false;

    private static void addCacheName(String cacheName){
        if(!cacheNameSet.contains(cacheName)){
            cacheNameSet.add(cacheName);
        }
    }

    /**
     * 设置是否检查在线状态，true：jwt过期，若30分钟内在线，可以继续访问
     * @param online
     */
    public static void setCheckOnline(boolean online){
        SecuritySession.online=online;
    }

    public static void setSession(String sessionName,Object value){
        addCacheName(sessionName);
        String sessionId=SecurityUtils.getLoginUser().getSessionId();
        cacheMap.put(sessionId,System.currentTimeMillis());
        getCache().setCacheMapValue(CacheConstants.SESSION_NAME,sessionId,System.currentTimeMillis());
        getCache().setCacheMapValue(sessionName,sessionId ,value);
    }

    public static Object getSession(String sessionName){
        String sessionId=SecurityUtils.getLoginUser().getSessionId();
        cacheMap.put(sessionId,System.currentTimeMillis());
        return getCache().getCacheMapValue(sessionName,sessionId);
    }

    private static void refreshCache(String sessionId){
        if(online){
            cacheMap.put(sessionId,System.currentTimeMillis());
        }
    }

    private static void clearSession(String sessionId){
        RedisCache redis=getCache();
        cacheMap.remove(sessionId);
        redis.delCacheMapValue(CacheConstants.SESSION_NAME,sessionId);
        for(String oth:cacheNameSet){
            redis.delCacheMapValue(oth,sessionId);
        }
    }

    /**
     * @return 返回true继续执行
     */
    public static boolean doFilter(HttpServletRequest req, HttpServletResponse resp){
        if(SecurityMatchers.matchLogout(req)){
            ServletUtils.renderString(resp, JSON.toJSONString(AjaxResult.success("退出成功")));
            try{
                getLoginService().recordLoginInfor(getLoginService().getRequestJwt(req).getUserId(), Constants.LOGOUT, "退出成功");
                clearSession(getLoginService().getRequestJwt(req).getCacheId());
            }catch (Exception e){}
            return false;
        }
        if(SecurityMatchers.match(req)){
            return true;
        }else if(loginByToken(req,resp)){
            return true;
        }
        return false;
    }

    private static boolean loginByToken(HttpServletRequest request,HttpServletResponse resp){
        LoginJwtHandler jwt=getLoginService().getRequestJwt(request);
        if(jwt==null){
            logger.error("请求[{}]，缺少token",request.getRequestURI());
            getLoginService().writeUnauthorized(resp);
            return false;
        }
        if(!jwt.valid()){
            logger.error("非法token：{}-{}", ServletUtils.getIp(request),jwt.getUserId());
            getLoginService().writeUnauthorized(resp);
            return false;
        }
        if(jwt.isExpired()){
            if(!online || online && cacheMap.get(jwt.getCacheId())==null){
                logger.error("请求[{}]，token失效[expire:{}]",request.getRequestURI());
                getLoginService().writeUnauthorized(resp);
                return false;
            }
        }
        refreshCache(jwt.getCacheId());
        LoginUser loginUsr=getLoginService().createLoginUser(jwt);
        SecurityUtils.setLoginUser(loginUsr);
        return true;
    }

    private static SecurityLoginService loginService=null;
    private static SecurityLoginService getLoginService(){
        if(loginService==null){
            loginService=SpringUtil.getBean(SecurityLoginService.class);
        }
        return loginService;
    }

    static {
        ThreadPools.fixedScheduleExecute(new Runnable() {//定时同步清理缓存
            @Override
            public void run() {
                try {
                    RedisCache redis=getCache();
                    if(redis==null){
                        logger.error("缺少redis缓存");
                        return;
                    }
                    String[] keys=cacheMap.keySet().toArray(new String[0]);
                    for(String sessionId:keys){
                        Long mapTime=cacheMap.get(sessionId);
                        if(mapTime+expireMillseconds<System.currentTimeMillis()){//超时
                            Long cacheTime=redis.getCacheMapValue(CacheConstants.SESSION_NAME,sessionId);
                            if(cacheTime!=null && cacheTime+expireMillseconds>System.currentTimeMillis()){//缓存未超时
                                cacheMap.put(sessionId,cacheTime);
                            }else{
                                cacheMap.remove(sessionId);
                                if(cacheTime!=null){
                                    redis.delCacheMapValue(CacheConstants.SESSION_NAME,sessionId);
                                    for(String oth:cacheNameSet){
                                        redis.delCacheMapValue(oth,sessionId);
                                    }
                                }
                            }
                        }else{
                            Long cacheTime=redis.getCacheMapValue(CacheConstants.SESSION_NAME,sessionId);
                            if(cacheTime==null || cacheTime<mapTime){
                                redis.setCacheMapValue(CacheConstants.SESSION_NAME,sessionId,mapTime);
                            }
                        }
                    }
                }catch (Exception e){
                    logger.error("缓存定时任务：",e);
                }
            }
        },10 * 60);
        ThreadPools.delayedExecute(new Runnable() {//启动加载缓存
            @Override
            public void run() {
                RedisCache redis=getCache();
                if(redis==null){
                    logger.error("缺少redis缓存");
                    return;
                }
                Map<String,Long>  map=redis.getCacheMap(CacheConstants.SESSION_NAME);
                cacheMap.putAll(map);
            }
        }, 10);
    }

    private static RedisCache getCache(){
        if(cache==null){
            cache= SpringUtil.getBean(RedisCache.class);
        }
        return cache;
    }
}
